import bisect
import datetime
import sxtwl
from lunar_python import Lunar
from .wuxing import WuXing
from .consts import common
from .consts import energy_consts
from .consts import shensha_consts
from .tiangan import Gan, GanChong, GanHe
from .dizhi import Zhi, ZhiXing, ZhiChong, ZhiPo, ZhiHai, ZhiAnHe, ZhiHe, Zhi3He, Zhi3Hui
from .shishen import ShiShen
from .zhangsheng import ZhangSheng
from .shensha import ShenSha
from .geju import zhuanwang, cong, huaqi, luren, general
from .zp_utils import combine


class MingPan:
    """
    命盘
    """
    def __init__(self, sizhu, gender='male', birthday=None):
        assert len(sizhu) == 4
        self.energy = {'木': 0, '火': 0, '土': 0,  '金': 0, '水': 0}
        self.roles = {'木': '', '火': '', '土': '',  '金': '', '水': ''}
        self.biroles = {'日主': '', '食伤': '', '财星': '',  '官杀': '', '印绶': ''}
        self.sizhu = [
            [Cell(self, 0, 0, sizhu[0][0]), Cell(self, 0, 1, sizhu[0][1])],
            [Cell(self, 1, 0, sizhu[1][0]), Cell(self, 1, 1, sizhu[1][1])],
            [Cell(self, 2, 0, sizhu[2][0]), Cell(self, 2, 1, sizhu[2][1])],
            [Cell(self, 3, 0, sizhu[3][0]), Cell(self, 3, 1, sizhu[3][1])],
        ]
        self.gender = gender.lower()
        self.birthday = birthday  # 出生时间

        self.riyuan = self.sizhu[2][0]      # 日主
        self.yueling = self.sizhu[1][1]     # 月令

        self.cang_gans = [[], [], [], []]   # 地支藏干
        self.calc_cang_gans()
        self.gans_shen = [[], [], [], []]   # 干神
        self.calc_gans_shen()
        self.zhis_shen = [[], [], [], []]   # 支神
        self.calc_zhis_shen()
        self.zhangsheng = [[], [], [], []]  # 长生
        self.self_zs = [[], [], [], []]  # 自坐
        self.lushen_zhis = [[], [], [], []]  # 禄位
        self.calc_zhangsheng()
        self.nayin = [[], [], [], []]
        self.calc_nayin()
        self.shensha = [[], [], [], []]     # 神煞
        self.calc_shensha()

        self.relationships = []     # 元素为Connected对象
        self.calc_relationships()

        self.my_energy = 0
        self.other_energy = 0
        self.max_energy_wuxing = None
        self.min_energy_wuxing = None
        self.yong_wuxing = None
        self.xi_wuxing = None
        self.calc_roles()
        self.calc_energy()

        self.geju = ''       # 命主格局
        self.calc_geju()
        self.calc_xi_yong()

        # 大运流年
        self.start_dayun = None
        self.dayun_options = []    # 长度为10
        self.liunian_options = []  # 长度为10
        self.dayun = None
        self.liunian = None
        self.calc_dayun()

    def is_strong(self):
        """是身强吗？"""
        return self.my_energy > self.other_energy

    def is_very_weak(self):
        """
        日主弱极
        """
        my_wx = self.riyuan.gan.wuxing
        for tg_cell, dz_cell in self.sizhu:
            if tg_cell != self.riyuan:
                if my_wx.same_as(tg_cell.gan.wuxing) or my_wx.bei_sheng(tg_cell.gan.wuxing):
                    if not tg_cell.is_hua():
                        return False
            if my_wx.same_as(dz_cell.zhi.wuxing) or my_wx.bei_sheng(dz_cell.zhi.wuxing):
                if not dz_cell.is_hua():
                    return False
        return True

    def calc_cang_gans(self):
        for i, zhu in enumerate(self.sizhu):
            dz = zhu[1].zhi
            self.cang_gans[i].extend(dz.gans)

    def calc_gans_shen(self):
        for i, zhu in enumerate(self.sizhu):
            if i == 2: continue
            tg = zhu[0].gan
            same_yy = self.riyuan.gan.yinyang == tg.yinyang
            distance = self.riyuan.gan.wuxing.distance(tg.wuxing)
            self.gans_shen[i].append(ShiShen.get_item(same_yy, distance))

    def calc_zhis_shen(self):
        for i, zhu in enumerate(self.sizhu):
            dz = zhu[1].zhi
            for cg in dz.gans:
                same_yy = self.riyuan.gan.yinyang == cg.yinyang
                distance = self.riyuan.gan.wuxing.distance(cg.wuxing)
                self.zhis_shen[i].append(ShiShen.get_item(same_yy, distance))

    def calc_zhangsheng(self):
        lushen_map = {item[0]: item[1] for item in shensha_consts.LUSHEN}
        for i, zhu in enumerate(self.sizhu):
            tg, dz = zhu[0].gan, zhu[1].zhi
            self.zhangsheng[i].append(
                ZhangSheng.get_item(self.riyuan.gan.name, dz.name)
            )
            self.self_zs[i].append(
                ZhangSheng.get_item(tg.name, dz.name)
            )
            self.lushen_zhis[i].append(lushen_map[tg.name])

    def calc_nayin(self):
        for i, zhu in enumerate(self.sizhu):
            jz = zhu[0].cell_name() + zhu[1].cell_name()
            idx = common.JIAZI.index(jz)
            nayin = common.NAYIN[idx//2]
            self.nayin[i].append(nayin)

    def calc_shensha(self):
        for item in [
            ShenSha.check_lushen(self),
            ShenSha.check_yangren(self),
            ShenSha.check_yima(self),
            ShenSha.check_taohua(self),
            ShenSha.check_huagai(self),
            ShenSha.check_kuigang(self),
            ShenSha.check_kongwang(self),
            ShenSha.check_ycyc(self),
            ShenSha.check_muku(self),
            ShenSha.check_tjgr(self),
            ShenSha.check_tygr(self),
            ShenSha.check_tcgr(self),
            ShenSha.check_gygr(self),
            ShenSha.check_ydgr(self),
            ShenSha.check_tdgr(self),
            ShenSha.check_sqgr(self),
            ShenSha.check_tggr(self),
            ShenSha.check_wcgr(self),
            ShenSha.check_jiangxing(self),
            ShenSha.check_yuanchen(self),
            ShenSha.check_ciguan(self),
            ShenSha.check_sedb(self),
            ShenSha.check_gls(self),
            ShenSha.check_zaisha(self),
        ]:
            for i, col in enumerate(item):
                self.shensha[i].extend(col)

    def calc_relationships(self):
        for items in [
            # WuXing.calc_zhu_shengke(self),
            # WuXing.calc_gan_shengke(self),
            # WuXing.calc_zhi_shengke(self),
            GanChong.calc_gan_chong(self),
            GanHe.calc_gan_he(self),

            ZhiXing.calc_zhi_xing(self),
            ZhiChong.calc_zhi_chong(self),
            ZhiPo.calc_zhi_po(self),
            ZhiHai.calc_zhi_hai(self),
            ZhiAnHe.calc_zhi_an_he(self),
            ZhiHe.calc_zhi_he(self),
            Zhi3He.calc_zhi_3he(self),
            Zhi3Hui.calc_zhi_3hui(self),

        ]:
            self.relationships.extend(items)

    def calc_energy(self):
        GE = energy_consts.GAN_ENERGY
        ZE = energy_consts.ZHI_ENERGY
        month_dz = self.yueling.zhi.name
        for tg_cell, dz_cell in self.sizhu:
            tg_cell.inc_energy(GE[month_dz][tg_cell.gan.name])
            for cg in dz_cell.zhi.gans:
                if cg.name in ZE[dz_cell.zhi.name]:
                    dz_cell.inc_energy(  # 有些藏干没有能量
                        ZE[dz_cell.zhi.name][cg.name][month_dz],
                        cg.wuxing.name
                    )
        self.calc_energy_step2()

    def calc_energy_step2(self):
        mp_wx = self.riyuan.gan.wuxing  # 日主五行
        self.max_energy_wuxing = mp_wx  # 使用任意一个五行初始化
        self.min_energy_wuxing = mp_wx  # 使用任意一个五行初始化
        for wx, weight in self.energy.items():
            wx_obj = WuXing.get_item(wx)
            if self.energy[self.max_energy_wuxing.name] < weight:
                self.max_energy_wuxing = wx_obj
            if self.energy[self.min_energy_wuxing.name] > weight:
                self.min_energy_wuxing = wx_obj
            if mp_wx.same_as(wx_obj) or mp_wx.bei_sheng(wx_obj):
                self.my_energy += weight
            else:
                self.other_energy += weight

    def calc_geju(self):
        """
        计算命主格局
        """
        methods = [
            (zhuanwang.wang_mu, '曲直格'),
            (zhuanwang.wang_huo, '炎上格'),
            (zhuanwang.wang_tu, '稼穑格'),
            (zhuanwang.wang_jin, '从革格'),
            (zhuanwang.wang_shui, '润下格'),

            (cong.cong_cai, '从财格'),
            (cong.cong_sha, '从杀格'),
            (cong.cong_er, '从儿格'),
            (cong.cong_wang, '从旺格'),
            (cong.cong_qiang, '从强格'),

            (huaqi.hua_tu, '化土格'),
            (huaqi.hua_jin, '化金格'),
            (huaqi.hua_shui, '化水格'),
            (huaqi.hua_mu, '化木格'),
            (huaqi.hua_huo, '化火格'),

            (luren.jianlu, '建禄格'),
            (luren.yueren, '月刃格'),
            (general.general_geju, '')
        ]
        for method, ans in methods:
            check = method(self)
            if check:
                self.geju = ans or check
                break

    def calc_roles(self):
        rizhu_wx = self.riyuan.gan.wuxing
        for wx in self.roles.keys():
            wx_obj = WuXing.get_item(wx)
            if rizhu_wx.same_as(wx_obj):
                self.roles[wx] = '日主'
                self.biroles['日主'] = wx
            if rizhu_wx.sheng(wx_obj):
                self.roles[wx] = '食伤'
                self.biroles['食伤'] = wx
            if rizhu_wx.ke(wx_obj):
                self.roles[wx] = '财星'
                self.biroles['财星'] = wx
            if rizhu_wx.bei_ke(wx_obj):
                self.roles[wx] = '官杀'
                self.biroles['官杀'] = wx
            if rizhu_wx.bei_sheng(wx_obj):
                self.roles[wx] = '印绶'
                self.biroles['印绶'] = wx

    def calc_xi_yong(self):
        """计算喜用神: 克耗最强阵营的最强五行"""
        if self.geju in ['曲直格', '化木格']:
            self.yong_wuxing = WuXing.get_item('木')
            self.xi_wuxing = WuXing.get_item('水')
        elif self.geju in ['炎上格', '化火格']:
            self.yong_wuxing = WuXing.get_item('火')
            self.xi_wuxing = WuXing.get_item('木')
        elif self.geju in ['稼穑格', '化土格']:
            self.yong_wuxing = WuXing.get_item('土')
            self.xi_wuxing = WuXing.get_item('火')
        elif self.geju in ['从革格', '化金格']:
            self.yong_wuxing = WuXing.get_item('金')
            self.xi_wuxing = WuXing.get_item('土')
        elif self.geju in ['润下格', '化水格']:
            self.yong_wuxing = WuXing.get_item('水')
            self.xi_wuxing = WuXing.get_item('金')
        elif self.geju in ['从旺格', '从强格']:
            self.yong_wuxing = WuXing.get_item(self.biroles['日主'])
            self.xi_wuxing = WuXing.get_item(self.biroles['印绶'])
        elif self.geju == '从财格':
            self.yong_wuxing = WuXing.get_item(self.biroles['财星'])
            self.xi_wuxing = WuXing.get_item(self.biroles['食伤'])
        elif self.geju == '从杀格':
            self.yong_wuxing = WuXing.get_item(self.biroles['官杀'])
            self.xi_wuxing = WuXing.get_item(self.biroles['财星'])
        elif self.geju == '从儿格':
            self.yong_wuxing = WuXing.get_item(self.biroles['食伤'])
            self.xi_wuxing = WuXing.get_item(self.biroles['日主'])
        else:  # 普通格和禄刃格按扶抑取用神
            my_mx_wx, other_mx_wx = None, None

            my_wx = self.riyuan.gan.wuxing
            for wx, e in self.energy.items():
                wx_obj = WuXing.get_item(wx)
                if my_wx.same_as(wx_obj) or my_wx.bei_sheng(wx_obj):
                    if my_mx_wx is None or e > self.energy[my_mx_wx.name]:
                        my_mx_wx = wx_obj
                else:
                    if other_mx_wx is None or e > self.energy[other_mx_wx.name]:
                        other_mx_wx = wx_obj
            if self.is_strong():
                target = my_mx_wx  # 我营最强五行
            else:
                target = other_mx_wx  # 他营最强五行
            for wx in self.energy.keys():
                wx_obj = WuXing.get_item(wx)
                if target.bei_ke(wx_obj):
                    self.yong_wuxing = wx_obj
                if target.ke(wx_obj):
                    self.xi_wuxing = wx_obj

    def calc_dayun(self):
        # 节令对象
        delta = None
        jl_obj = Lunar.fromDate(self.birthday) if self.birthday else None

        JZ = common.JIAZI * 2  # 两遍甲子
        y_gan = self.sizhu[0][0].gan
        m_gan, m_zhi = self.sizhu[1][0].gan, self.yueling.zhi
        index = JZ.index(m_gan.name+m_zhi.name)
        if self.is_male() == y_gan.yinyang.is_yang():
            start, end = index, index+10
            self.dayun_options = JZ[start+1:end+1]
            if self.birthday:
                next_jie = jl_obj.getNextJie()
                next_jie_dt = next_jie._JieQi__solar._Solar__calendar
                delta = next_jie_dt - self.birthday
        else:
            start, end = index-10, index
            if start < 0:
                start, end = start+60, end+60
            self.dayun_options = JZ[start:end][::-1]
            if self.birthday:
                prev_jie = jl_obj.getPrevJie()
                prev_jie_dt = prev_jie._JieQi__solar._Solar__calendar
                delta = self.birthday - prev_jie_dt
        if self.birthday:
            current = datetime.datetime.now()
            self.start_dayun = self.birthday + datetime.timedelta(days=delta.days*120, seconds=delta.seconds*120)
            print(self.start_dayun)
            years = [self.start_dayun.year + item*10 for item in range(10)]
            index = bisect.bisect_right(years, current.year)
            if index > 0:
                loop_year = years[index-1]
                self.set_dayun(self.dayun_options[index-1])
                ll_idx = current.year - loop_year
                self.set_liunian(self.liunian_options[ll_idx])
        if not self.dayun:
            self.set_dayun(self.dayun_options[0])


    def set_dayun(self, zhu):
        """
        固定大运
        """
        dayun_index = self.dayun_options.index(zhu)
        if dayun_index >= 0:
            self.dayun = zhu
            self.liunian = None
            if self.birthday:  # 知道生日才能推流年
                year = datetime.datetime(
                    year=self.start_dayun.year + dayun_index*10,
                    month=self.start_dayun.month,
                    day=self.start_dayun.day,
                )
                lunar = sxtwl.Lunar()
                lunar_day = lunar.getDayBySolar(year.year, year.month, year.day)
                gans = [g[0] for g in common.GANS]
                zhis = [z[0] for z in common.ZHIS]
                gz = gans[lunar_day.Lyear2.tg] + zhis[lunar_day.Lyear2.dz]
                JZ = common.JIAZI * 2  # 两遍甲子
                liunian_index = JZ.index(gz)
                self.liunian_options = JZ[liunian_index:liunian_index+10]

    def set_liunian(self, zhu):
        """
        固定流年
        """
        if not self.liunian_options:
            print('生辰八字生成的命盘没有大运流年')
        else:
            liunian_index = self.liunian_options.index(zhu)
            if liunian_index >= 0:
                self.liunian = zhu

    def calc_dyln_relationships(self):
        ans = []
        xs = []
        if self.dayun:
            xs.append(self.dayun)
        if self.liunian:
            xs.append(self.liunian)
        for item in xs:
            tg, dz = item
            for zhu in self.sizhu:
                ans.append(GanHe.get_item(tg, zhu[0].cell_name()))
                ans.append(GanChong.get_item(tg, zhu[0].cell_name()))

                ans.append(ZhiXing.get_item([dz, zhu[1].cell_name()]))
                ans.append(ZhiChong.get_item(dz, zhu[1].cell_name()))
                ans.append(ZhiPo.get_item(dz, zhu[1].cell_name()))
                ans.append(ZhiHai.get_item(dz, zhu[1].cell_name()))
                ans.append(ZhiAnHe.get_item(dz, zhu[1].cell_name()))
                ans.append(ZhiHe.get_item(dz, zhu[1].cell_name()))

            for zhu1, zhu2 in combine(self.sizhu, 2):
                ans.append(ZhiXing.get_item([dz, zhu1[1].cell_name(), zhu2[1].cell_name()]))
                ans.append(Zhi3He.get_item(dz, zhu1[1].cell_name(), zhu2[1].cell_name()))
                ans.append(Zhi3Hui.get_item(dz, zhu1[1].cell_name(), zhu2[1].cell_name()))

        if len(xs) == 2:
            ans.append(GanHe.get_item(xs[0][0], xs[1][0]))
            ans.append(GanChong.get_item(xs[0][0], xs[1][0]))

            ans.append(ZhiXing.get_item([xs[0][1], xs[1][1]]))
            ans.append(ZhiChong.get_item(xs[0][1], xs[1][1]))
            ans.append(ZhiPo.get_item(xs[0][1], xs[1][1]))
            ans.append(ZhiHai.get_item(xs[0][1], xs[1][1]))
            ans.append(ZhiAnHe.get_item(xs[0][1], xs[1][1]))
            ans.append(ZhiHe.get_item(xs[0][1], xs[1][1]))

            for zhu in self.sizhu:
                ans.append(ZhiXing.get_item([xs[0][1], xs[1][1], zhu[1].cell_name()]))
                ans.append(Zhi3He.get_item(xs[0][1], xs[1][1], zhu[1].cell_name()))
                ans.append(Zhi3Hui.get_item(xs[0][1], xs[1][1], zhu[1].cell_name()))

        return [str(item) for item in ans if item]

    def inc_energy(self, wuxing, delta):
        self.energy[wuxing] += delta

    def is_male(self):
        return self.gender == 'male'

    def calc_tags(self):
        # 比劫，食伤，财星，官杀，印枭
        shishen_bitset = [[], [], [], [], []]
        shishen_of_house = []
        this = self.riyuan.gan
        for i, zhu in enumerate(self.sizhu):
            tg, dz = zhu[0].gan, zhu[1].zhi
            if i != 2:
                same_yy = this.yinyang == tg.yinyang
                distance = this.wuxing.distance(tg.wuxing)
                shishen_bitset[distance].append(same_yy)
                shishen_of_house.append(ShiShen.get_item(same_yy, distance))
            same_yy = this.yinyang == dz.yinyang
            if dz.wuxing.name in '水火':
                same_yy = not same_yy
            distance = this.wuxing.distance(dz.wuxing)
            shishen_of_house.append(ShiShen.get_item(same_yy, distance))

        shishen_combines = []
        if len(shishen_bitset[0]) > 1:
            shishen_combines.append('比劫重重')
        if shishen_bitset[0] and shishen_bitset[2]:
            shishen_combines.append('比劫分财')
        if shishen_bitset[3] and shishen_bitset[4]:
            shishen_combines.append('官印相生')
        if shishen_bitset[2] and shishen_bitset[3]:
            shishen_combines.append('财官相生')
        if True in shishen_bitset[3] and False in shishen_bitset[3]:
            shishen_combines.append('官杀混杂')
        if False in shishen_bitset[1] and shishen_bitset[3]:
            shishen_combines.append('伤官见官')
        if False in shishen_bitset[1] and False in shishen_bitset[4]:
            shishen_combines.append('伤官配印')
        if True in shishen_bitset[1] and True in shishen_bitset[3]:
            shishen_combines.append('食神制杀')
        if shishen_bitset[2] and shishen_bitset[4]:
            shishen_combines.append('财破印')
        if shishen_bitset[1] and shishen_bitset[2]:
            shishen_combines.append('食伤生财')
        if shishen_bitset[4] and True in shishen_bitset[1]:
            shishen_combines.append('枭印夺食')
        return {
            '格局': self.geju,
            '日干月建': f'{self.riyuan.gan.name}生{self.yueling.zhi.name}月',
            '最强五行': self.max_energy_wuxing.name,
            '最弱五行': self.min_energy_wuxing.name,
            '天干十神组合': shishen_combines,
            '宫位十神组合': [  # 七条
                f'{x}·{y}' for x, y in zip(['年干', '年支', '月干', '月支', '日支', '时干', '时支'], shishen_of_house)
            ],
        }

    def dayun_liunian(self):
        ans = {
            'ten_year': self.dayun,
            'year': self.liunian,
            'dyln_relationships': self.calc_dyln_relationships(),
        }
        if self.start_dayun:
            years = [self.start_dayun.year + item*10 for item in range(10)]
        else:
            years = ['' for _ in range(10)]
        ans['dayun'] = [{
            'name': n,
            'year': y,
            'loop': n == self.dayun
        } for n, y in zip(self.dayun_options, years)]
        if self.birthday:
            loop_year = self.start_dayun.year + self.dayun_options.index(self.dayun) * 10
            ans['liunian'] = [{
                'name': n,
                'year': y,
                'this_year': n == self.liunian
            } for n, y in zip(self.liunian_options, [loop_year+x for x in range(10)])]
        return ans

    def to_json(self):
        ans = {
            'gans_shen': [[i.name for i in col] for col in self.gans_shen],
            'zhis_shen': [[i.name for i in col] for col in self.zhis_shen],
            'gans': [[f'{col[0].gan.name}·{col[0].gan.yinyang.name}{col[0].gan.wuxing.name}'] for col in self.sizhu],
            'zhis': [[f'{col[1].zhi.name}·{col[1].zhi.yinyang.name}{col[1].zhi.wuxing.name}'] for col in self.sizhu],
            'cang_gans': [[f'{g.name}·{g.yinyang.name}{g.wuxing.name}' for g in col] for col in self.cang_gans],
            'zhangsheng': [[i.name for i in col] for col in self.zhangsheng],
            'self_zs': [[i.name for i in col] for col in self.self_zs],  # 自坐
            'lushen_zhis': [col for col in self.lushen_zhis],  # 禄位
            'nayin': self.nayin,  # 纳音
            'shensha': [[i.name for i in col] for col in self.shensha],
            'geju': self.geju,
            'yong_shen': self.yong_wuxing.name,
            'xi_shen': self.xi_wuxing.name,
            'relationships': [str(item) for item in self.relationships],
            'title': f'{ self.riyuan.gan.wuxing.name }命·身{ "强" if self.is_strong() else "弱" }',
            'is_strong': self.is_strong(),
            'energy': self.energy,
            'roles': self.roles,
        }
        ans.update(self.dayun_liunian())
        return ans


class Cell:
    """
    宫位：x行，y列，大运流年的随便
    """
    def __init__(self, mingpan, x, y, name):
        self.mingpan = mingpan  # 所属命盘
        self.x = x
        self.y = y
        self.connections = []
        self.gan = None
        self.zhi = None
        # 宫位能量: 八字分字定能量, 只需要一个数值
        # 五行就是宫位干支的五行，通过self.wuxing()获取
        self.cell_energy_factors = {}  # 宫位能量关系衰减因子，默认为1
        self.cell_energy = 0

        self.energy_factors = {}  # 五行命盘能量关系衰减因子，默认为1
        self.energy = {}
        if y == 0:  # 第一行天干
            self.gan = Gan.get_item(name)
        else:  # 第二行地支
            self.zhi = Zhi.get_item(name)

    def cell_name(self):
        return self.gan and self.gan.name or self.zhi and self.zhi.name

    def wuxing(self):
        element = self.gan or self.zhi
        return element.wuxing.name

    def inc_energy(self, delta, wx=None):
        wx = wx or self.wuxing()
        self.energy[wx] = self.energy.get(wx, 0) + delta
        self.mingpan.inc_energy(wx, delta)

    def this_energy(self):
        return self.energy[self.wuxing()]

    def reset_energy(self):
        for wx, power in self.energy.items():
            self.mingpan.inc_energy(wx, -power)
        self.energy = {}

    def is_hua(self):
        """
        是否被制化
        """
        for conn in self.connections:
            if hasattr(conn.relation, 'wuxing'):
                return True
        return False

    def is_tg(self):
        return self.zhi is None

    def is_dz(self):
        return self.gan is None

    def same_as(self, other):
        return self.is_dz() == other.is_dz()

    def __str__(self):
        if self.y == 0:
            return self.gan.name
        else:
            return self.zhi.name